前端进阶之旅前端进阶之旅
基础篇
进阶篇
高频篇
精选篇
手写篇
面经篇
AI 篇
原理篇
每日一题
小程序题库
知识卡片
  • 场景篇按分类整理的大前端场景考点
  • 历年面经按年份追踪真实考点
  • 算法题库NEW在线编码即时判题
  • 专项自测100 题快速查漏
  • 前端基础
    • HTTP从报文一路讲到 HTTPS
    • 浏览器渲染、事件循环、进程
    • 计算机基础Linux、网络、操作系统
  • 进阶专项
    • 设计模式23 种模式怎么用
    • 前端系统进阶学习大型项目工程化
    • 前端综合文章长期沉淀的实践文
  • 工程与工具
    • Node学习指南从环境搭建到服务端
    • NPM工作流script、依赖与发布
    • Docker容器化部署上手
    • Canvas图形与动画实战
  • 路线与导图
    • 思维导图知识点全景图
    • 学习路线按图索骥不跑偏
  • 动态
    • 公众号动态公众号历史文章
    • 博客动态站长的技术博客
    • 开发者导航常用工具与文档站
  • AI 助手随时提问,即时解析
  • AI 模拟面试模拟真实面试 + 报告
  • AI 知识地图串起全站知识点
  • AI 定制路线按你的简历现排
AI 热点
旧版
基础篇
进阶篇
高频篇
精选篇
手写篇
面经篇
AI 篇
原理篇
每日一题
小程序题库
知识卡片
  • 场景篇按分类整理的大前端场景考点
  • 历年面经按年份追踪真实考点
  • 算法题库NEW在线编码即时判题
  • 专项自测100 题快速查漏
  • 前端基础
    • HTTP从报文一路讲到 HTTPS
    • 浏览器渲染、事件循环、进程
    • 计算机基础Linux、网络、操作系统
  • 进阶专项
    • 设计模式23 种模式怎么用
    • 前端系统进阶学习大型项目工程化
    • 前端综合文章长期沉淀的实践文
  • 工程与工具
    • Node学习指南从环境搭建到服务端
    • NPM工作流script、依赖与发布
    • Docker容器化部署上手
    • Canvas图形与动画实战
  • 路线与导图
    • 思维导图知识点全景图
    • 学习路线按图索骥不跑偏
  • 动态
    • 公众号动态公众号历史文章
    • 博客动态站长的技术博客
    • 开发者导航常用工具与文档站
  • AI 助手随时提问,即时解析
  • AI 模拟面试模拟真实面试 + 报告
  • AI 知识地图串起全站知识点
  • AI 定制路线按你的简历现排
AI 热点
旧版
  • 基础篇

    • 环境搭建
    • node部署
    • 基础应用
  • 内置模块

    • 1.0 本地路径处理 path
    • 2.0 文件系统操作 fs
    • 3.1 基础调试 console
    • 3.2 本地调试远程服务器上的Node代码
    • 4.1 网络服务 http
    • 4.2 网络服务 http res
    • 4.3 网络服务 http req
    • 4.4 网络服务 http server
    • 4.5 网络服务 http client
    • 4.6 网络服务 https
    • 4.7 网络TCP net
    • 4.8 网络UDP dgram
    • 4.9 域名解析 dns
    • 5.0 网络地址解析 url
    • 5.1 URL查询字符串 querystring
    • 6.1 流操作 stream
    • 6.2 逐行读取 readline
    • 7.1 进程相关 process
    • 7.2 子进程 child
    • 8.1 二进制数据 buffer
    • 8.2 二进制解码 string_decoder
    • 9.1 事件机制 events
    • 9.2 实用工具模块 util
    • 9.3 数据加密 crypto
    • 9.4 MD5入门介绍及crypto模块的应用
    • 9.4 资源压缩 zlib
    • 9.5 集群 cluster
    • 9.6 v8
  • 进阶篇

    • 5分钟入门非对称加密用法
    • cluster
    • cookie_parser深入
    • crypto模块之理论篇
    • express+cookie_parser签名机制深入剖析
    • express+session实现简易身份认证
    • https
    • log4js入门实例
    • node8_napi
    • 使用 async 控制并发
    • 使用 eventproxy 控制并发
    • 使用 superagent 与 cheerio 完成简单爬虫
    • 基于express+muter的文件上传
    • 将图片转成datauri嵌入到html
    • 常用中间件 body_parser 实现解析
    • 日志模块morgan
    • 服务端字符编解码&乱码处理 charset_enc_dec
    • 测试用例mocha,should,istanbul
    • 调试日志打印debug模块
  • Express

    • 1.1 Express概览篇
    • 1.2 Express
  • Koa2

    • 1.0 koa2概览篇
    • 1.1 快速开始
    • 1.2 async await使用
    • 1.3 koa2简析结构
    • 1.4 koa中间件开发与使用
    • 10.1 单元测试
    • 11.1 开发debug
    • 12.1 快速启动
    • 12.2 框架设计
    • 12.3 分层操作
    • 12.4 数据库设计
    • 12.5 路由设计
    • 12.6 webpack4环境搭建
    • 12.7 使用react
    • 12.8 登录注册功能实现
    • 12.9 session登录态判断处理
    • 13.1 import export使用
    • 2.1 原生koa2实现路由
    • 2.2 koa router中间
    • 3.1 GET请求数据获取
    • 3.2 POST请求数据获取
    • 3.3 koa bodyparser中间件
    • 4.1 原生koa2实现静态资源服务器
    • 4.2 koa static中间件
    • 5.1 koa2使用cookie
    • 5.2 koa2实现session
    • 6.1 koa2加载模板引擎
    • 6.2 ejs模板引擎
    • 7.1 busboy模块
    • 7.2 上传文件简单实现
    • 7.3 异步上传图片实现
    • 8.1 mysql模块
    • 8.2 async await封装使用mysql
    • 8.3 项目建表初始化
    • 9.1 原生koa2实现JSONP
    • 9.2 koa jsonp中间件
  • 其他

    • 操作数据库
    • Session 与 Token
    • Cookie、Session、Token、JWT
    • Socket

# 服务端字符编解码&乱码处理 charset_enc_dec

30 秒速记

  • 乱码的本质:编码用的规则和解码用的规则不一样,字节被「翻译错了」
  • 排查顺序:拿到原始字节(hex)→ 看声明的编码 → 看实际用什么解码,别反复 toString 碰运气
  • 两种经典乱码:UTF-8 字节按 GBK 读 → 「浣犲ソ」;GBK 字节按 UTF-8 读 → ��
  • 流式数据里中文可能被切在两个 chunk 之间 → 先 Buffer.concat 再解码,或用 StringDecoder
  • Content-Type 的 charset、HTML 的 meta、数据库连接编码要统一,新系统一律 UTF-8

乱码说白了就是编码和解码用的不是同一套规则,同样的字节被翻译成了别的字。 服务端排查时我第一件事是把原始字节用 hex 打出来,比如「你」在 UTF-8 里是 e4 bd a0,在 GBK 里是 c4 e3,一看字节就知道发送方用的是哪种编码。然后对照接口声明的 charset 和代码里实际怎么解码的,哪一环对不上问题就在哪。另外流式读取时中文一个字占好几个字节,可能被切在两个 chunk 里,逐块 toString 会把半个字弄坏。根治的办法是全链路统一 UTF-8。

const buf = Buffer.from('你好') // Node 默认按 UTF-8 编码
console.log(buf.toString('hex')) // e4bda0e5a5bd
console.log(new TextDecoder('gbk').decode(buf)) // 浣犲ソ  ← 按 GBK 读就乱了
@前端进阶之旅: 代码已经复制到剪贴板

理解「服务端字符编解码&乱码处理 charset_enc_dec」时,要先明确输入、运行阶段和可观察输出。这里的交互实验会分别执行正常路径与失败路径,并把 Node.js 的关键状态变化和同屏代码对应起来;不能只记命令名称。

工程中还要验证退出码、资源清理和异常输入。属于 network 场景的配置应保持最小可复现,并在开发环境与 CI 中使用相同命令复跑,避免把平台差异或隐式默认值带到线上。

服务端处理文本时,经过的每一个边界都可能发生一次编码或解码:请求体从网络进来是字节,要解码成字符串;字符串写数据库,要按连接编码转成字节;读文件、写日志、返回响应也是一样。乱码只会出现在某一次「编码规则和解码规则不一致」的边界上,排查就是沿着链路找到第一个出错的边界。

实操上有三个习惯很管用。

第一,在边界处打出原始字节。buf.toString('hex') 或者 xxd file.txt,对照已知字符的编码就能判断:「你」在 UTF-8 是 e4 bd a0,在 GBK 是 c4 e3。

第二,认识两种最常见的乱码长相。UTF-8 字节被当成 GBK 读,会出现「浣犲ソ」「鍙戦€」这种生僻汉字;GBK 字节被当成 UTF-8 读,会出现大量 �。前者往往还能救回来,后者原始信息已经丢了。

第三,流式数据别逐块解码。一个中文字符在 UTF-8 里占 3 个字节,网络分块可能正好把它切开:

const { StringDecoder } = require('string_decoder')
const buf = Buffer.from('你') // e4 bd a0
const a = buf.subarray(0, 2), b = buf.subarray(2)

console.log(a.toString() + b.toString()) // ���  逐块解码,字被切坏

const decoder = new StringDecoder('utf8')
console.log(decoder.write(a) + decoder.write(b)) // 你  残缺的字节会留到下一块
@前端进阶之旅: 代码已经复制到剪贴板

Node 的 Buffer.toString() 只认 utf8、utf16le、latin1 等少数编码,处理 GBK、Big5 这类编码要用 iconv-lite,或者在带完整 ICU 的 Node 里用 new TextDecoder('gbk')。新系统的协议最好直接约定 UTF-8,并在 Content-Type 里写明 charset=utf-8。

面试官追问

追问 1页面上中文显示正常,写进日志就乱码,说明是日志的问题吗?
参考回答

不一定,只能说明页面那条路径的编解码是对的。可能是日志写入时编码不对,也可能是你看日志的终端或编辑器用了 GBK,先 hexdump 看文件里的字节是不是 UTF-8 再下结论。

追问 2看到「锟斤拷」这种乱码,大概能判断出什么?
参考回答

说明数据被错误解码过两次。先按 UTF-8 解码失败变成了替换字符 U+FFFD,它的 UTF-8 字节是 ef bf bd,两个连起来再按 GBK 读就是「锟斤拷」。到这一步原始数据已经丢了,只能回源头查。

追问 3乱码字符串能不能转回来?
参考回答

看损坏程度。UTF-8 字节被当 GBK 解出来的「浣犲ソ」,再按 GBK 编码回去、按 UTF-8 解码通常能恢复;已经变成 � 的就不行了,信息在解码那一步就丢了。

追问 4用户名里的 emoji 存进 MySQL 后变成了问号,是编解码问题吗?
参考回答

多半是表或连接用的 utf8,它在 MySQL 里最多只存 3 个字节,emoji 要 4 个字节。改成 utf8mb4,表、字段和连接参数都要改。

# 写在前面

30 秒速记

  • 服务端每天都在做编解码:读请求体、读写文件、写数据库、返回响应
  • 网络和磁盘上只有字节,「字符串」是按某种编码解释出来的结果
  • 英文正常不代表没问题:ASCII 范围内 UTF-8、GBK 的字节一样,中文才会暴露差异
  • 二进制(图片、压缩包)不要转字符串,全程保持 Buffer
  • 解决乱码的思路永远是:找到原始字节,确认它真实的编码,用对应的规则解码

服务端开发绕不开字符编解码,因为网络和磁盘上流动的只有字节,字符串是按某种编码规则解释出来的。 读请求体、读文件、查数据库、写响应,每一步都在字节和字符串之间转换,只要有一步用错规则就是乱码。很多人遇到乱码就挨个试 toString('utf8')、toString('latin1'),其实效率很低。正确的思路是先确认手上这段字节到底是按什么编码生成的,再用同一套规则解回来。还有个常见误判:英文没问题不代表编码对了,因为英文字母在 UTF-8 和 GBK 里字节是一样的。

在web服务端开发中,字符的编解码几乎每天都要打交道。编解码一旦处理不当,就会出现令人头疼的乱码问题。

不少从事node服务端开发的同学,由于对字符编码码相关知识了解不足,遇到问题时,经常会一筹莫展,花大量的时间在排查、解决问题。

文本先对字符编解码的基础知识进行简单介绍,然后举例说明如何在node中进行编解码,最后是服务端的代码案例。

面试官追问

追问 1接口收英文一直正常,收中文就乱码,后端说网络没问题,你怎么看?
参考回答

这恰好说明是编码不一致。ASCII 范围的字符在 UTF-8、GBK 里字节完全相同,怎么解都对;中文的字节不同,规则一错就乱。

追问 2上传接口文本和图片走同一条链路,代码里先统一 toString() 再处理,有什么问题?
参考回答

图片会被损坏。图片字节里有大量不合法的 UTF-8 序列,toString() 会把它们替换成 U+FFFD,再转回 Buffer 已经不是原图了。二进制全程保持 Buffer,只有确定是文本才解码。

追问 3要导入一批历史 GBK 文件,产品要求统一按 UTF-8 读,能直接这么干吗?
参考回答

不行,GBK 字节按 UTF-8 解出来就是一堆 �,而且无法恢复。导入时用 iconv.decode(buf, 'gbk') 先转成字符串,系统内部再统一存 UTF-8。

追问 4线上用户名偶发乱码,前端、后端、数据库三方都说自己没问题,怎么定位?
参考回答

沿链路在每个边界打出字节:请求进来的原始 Buffer、写库前的字符串、库里存的字节、响应出去的字节。第一个对不上的位置就是问题所在,只看最终页面是分不出来的。

# 关于字符编解码

30 秒速记

  • 网络里传的永远是二进制,不管是中文、英文、文本还是图片
  • 编码:字符串 → 字节(客户端发送前);解码:字节 → 字符串(服务端收到后)
  • 字节本身不带「我是什么编码」的信息,双方必须事先约定,或者在 Content-Type 的 charset 里声明
  • Node 里对应 Buffer.from(str, 'utf8') 编码、buf.toString('utf8') 解码

编解码就是字符和二进制之间的互相翻译:发送方把字符串编码成字节传出去,接收方再把字节解码回字符串。 比如客户端发「你好」,网络上实际跑的是 e4 bd a0 e5 a5 bd 这 6 个字节,服务端要按 UTF-8 解码才能还原成「你好」。关键在于字节本身不会告诉你它是用什么编码的,所以双方必须提前约定好,或者在 HTTP 头里写明 charset。约定不一致,同样的字节就会被翻译成完全不同的字。

const bytes = Buffer.from('你好', 'utf8') // 编码
console.log(bytes) // <Buffer e4 bd a0 e5 a5 bd>
console.log(bytes.toString('utf8')) // 你好  解码
@前端进阶之旅: 代码已经复制到剪贴板

基础篇
进阶篇
高频篇
精选篇
手写篇
面经篇
AI 篇
原理篇
每日一题
小程序题库
知识卡片
  • 场景篇按分类整理的大前端场景考点
  • 历年面经按年份追踪真实考点
  • 算法题库NEW在线编码即时判题
  • 专项自测100 题快速查漏
  • 前端基础
    • HTTP从报文一路讲到 HTTPS
    • 浏览器渲染、事件循环、进程
    • 计算机基础Linux、网络、操作系统
  • 进阶专项
    • 设计模式23 种模式怎么用
    • 前端系统进阶学习大型项目工程化
    • 前端综合文章长期沉淀的实践文
  • 工程与工具
    • Node学习指南从环境搭建到服务端
    • NPM工作流script、依赖与发布
    • Docker容器化部署上手
    • Canvas图形与动画实战
  • 路线与导图
    • 思维导图知识点全景图
    • 学习路线按图索骥不跑偏
  • 动态
    • 公众号动态公众号历史文章
    • 博客动态站长的技术博客
    • 开发者导航常用工具与文档站
  • AI 助手随时提问,即时解析
  • AI 模拟面试模拟真实面试 + 报告
  • AI 知识地图串起全站知识点
  • AI 定制路线按你的简历现排
AI 热点
旧版
  • 基础篇

    • 环境搭建
    • node部署
    • 基础应用
  • 内置模块

    • 1.0 本地路径处理 path
    • 2.0 文件系统操作 fs
    • 3.1 基础调试 console
    • 3.2 本地调试远程服务器上的Node代码
    • 4.1 网络服务 http
    • 4.2 网络服务 http res
    • 4.3 网络服务 http req
    • 4.4 网络服务 http server
    • 4.5 网络服务 http client
    • 4.6 网络服务 https
    • 4.7 网络TCP net
    • 4.8 网络UDP dgram
    • 4.9 域名解析 dns
    • 5.0 网络地址解析 url
    • 5.1 URL查询字符串 querystring
    • 6.1 流操作 stream
    • 6.2 逐行读取 readline
    • 7.1 进程相关 process
    • 7.2 子进程 child
    • 8.1 二进制数据 buffer
    • 8.2 二进制解码 string_decoder
    • 9.1 事件机制 events
    • 9.2 实用工具模块 util
    • 9.3 数据加密 crypto
    • 9.4 MD5入门介绍及crypto模块的应用
    • 9.4 资源压缩 zlib
    • 9.5 集群 cluster
    • 9.6 v8
  • 进阶篇

    • 5分钟入门非对称加密用法
    • cluster
    • cookie_parser深入
    • crypto模块之理论篇
    • express+cookie_parser签名机制深入剖析
    • express+session实现简易身份认证
    • https
    • log4js入门实例
    • node8_napi
    • 使用 async 控制并发
    • 使用 eventproxy 控制并发
    • 使用 superagent 与 cheerio 完成简单爬虫
    • 基于express+muter的文件上传
    • 将图片转成datauri嵌入到html
    • 常用中间件 body_parser 实现解析
    • 日志模块morgan
    • 服务端字符编解码&乱码处理 charset_enc_dec
      • 写在前面
      • 关于字符编解码
      • 关于字符集和字符编码
      • 字符编解码例子
      • 实际例子:服务端编解码
      • 相关链接
    • 测试用例mocha,should,istanbul
    • 调试日志打印debug模块
  • Express

    • 1.1 Express概览篇
    • 1.2 Express
  • Koa2

    • 1.0 koa2概览篇
    • 1.1 快速开始
    • 1.2 async await使用
    • 1.3 koa2简析结构
    • 1.4 koa中间件开发与使用
    • 10.1 单元测试
    • 11.1 开发debug
    • 12.1 快速启动
    • 12.2 框架设计
    • 12.3 分层操作
    • 12.4 数据库设计
    • 12.5 路由设计
    • 12.6 webpack4环境搭建
    • 12.7 使用react
    • 12.8 登录注册功能实现
    • 12.9 session登录态判断处理
    • 13.1 import export使用
    • 2.1 原生koa2实现路由
    • 2.2 koa router中间
    • 3.1 GET请求数据获取
    • 3.2 POST请求数据获取
    • 3.3 koa bodyparser中间件
    • 4.1 原生koa2实现静态资源服务器
    • 4.2 koa static中间件
    • 5.1 koa2使用cookie
    • 5.2 koa2实现session
    • 6.1 koa2加载模板引擎
    • 6.2 ejs模板引擎
    • 7.1 busboy模块
    • 7.2 上传文件简单实现
    • 7.3 异步上传图片实现
    • 8.1 mysql模块
    • 8.2 async await封装使用mysql
    • 8.3 项目建表初始化
    • 9.1 原生koa2实现JSONP
    • 9.2 koa jsonp中间件
  • 其他

    • 操作数据库
    • Session 与 Token
    • Cookie、Session、Token、JWT
    • Socket
完整面试题地址:
作者:程序员poetry
扫码关注作者公众号:「前端进阶之旅」 每天分享技术干货
前端进阶之旅公众号二维码

服务端字符编解码&乱码处理 charset_enc_dec|Nodejs专题系列

# 服务端字符编解码&乱码处理 charset_enc_dec

30 秒速记

  • 乱码的本质:编码用的规则和解码用的规则不一样,字节被「翻译错了」
  • 排查顺序:拿到原始字节(hex)→ 看声明的编码 → 看实际用什么解码,别反复 toString 碰运气
  • 两种经典乱码:UTF-8 字节按 GBK 读 → 「浣犲ソ」;GBK 字节按 UTF-8 读 → ��
  • 流式数据里中文可能被切在两个 chunk 之间 → 先 Buffer.concat 再解码,或用 StringDecoder
  • Content-Type 的 charset、HTML 的 meta、数据库连接编码要统一,新系统一律 UTF-8

乱码说白了就是编码和解码用的不是同一套规则,同样的字节被翻译成了别的字。 服务端排查时我第一件事是把原始字节用 hex 打出来,比如「你」在 UTF-8 里是 e4 bd a0,在 GBK 里是 c4 e3,一看字节就知道发送方用的是哪种编码。然后对照接口声明的 charset 和代码里实际怎么解码的,哪一环对不上问题就在哪。另外流式读取时中文一个字占好几个字节,可能被切在两个 chunk 里,逐块 toString 会把半个字弄坏。根治的办法是全链路统一 UTF-8。

const buf = Buffer.from('你好') // Node 默认按 UTF-8 编码
console.log(buf.toString('hex')) // e4bda0e5a5bd
console.log(new TextDecoder('gbk').decode(buf)) // 浣犲ソ  ← 按 GBK 读就乱了
@前端进阶之旅: 代码已经复制到剪贴板

理解「服务端字符编解码&乱码处理 charset_enc_dec」时,要先明确输入、运行阶段和可观察输出。这里的交互实验会分别执行正常路径与失败路径,并把 Node.js 的关键状态变化和同屏代码对应起来;不能只记命令名称。

工程中还要验证退出码、资源清理和异常输入。属于 network 场景的配置应保持最小可复现,并在开发环境与 CI 中使用相同命令复跑,避免把平台差异或隐式默认值带到线上。

服务端处理文本时,经过的每一个边界都可能发生一次编码或解码:请求体从网络进来是字节,要解码成字符串;字符串写数据库,要按连接编码转成字节;读文件、写日志、返回响应也是一样。乱码只会出现在某一次「编码规则和解码规则不一致」的边界上,排查就是沿着链路找到第一个出错的边界。

实操上有三个习惯很管用。

第一,在边界处打出原始字节。buf.toString('hex') 或者 xxd file.txt,对照已知字符的编码就能判断:「你」在 UTF-8 是 e4 bd a0,在 GBK 是 c4 e3。

第二,认识两种最常见的乱码长相。UTF-8 字节被当成 GBK 读,会出现「浣犲ソ」「鍙戦€」这种生僻汉字;GBK 字节被当成 UTF-8 读,会出现大量 �。前者往往还能救回来,后者原始信息已经丢了。

第三,流式数据别逐块解码。一个中文字符在 UTF-8 里占 3 个字节,网络分块可能正好把它切开:

const { StringDecoder } = require('string_decoder')
const buf = Buffer.from('你') // e4 bd a0
const a = buf.subarray(0, 2), b = buf.subarray(2)

console.log(a.toString() + b.toString()) // ���  逐块解码,字被切坏

const decoder = new StringDecoder('utf8')
console.log(decoder.write(a) + decoder.write(b)) // 你  残缺的字节会留到下一块
@前端进阶之旅: 代码已经复制到剪贴板

Node 的 Buffer.toString() 只认 utf8、utf16le、latin1 等少数编码,处理 GBK、Big5 这类编码要用 iconv-lite,或者在带完整 ICU 的 Node 里用 new TextDecoder('gbk')。新系统的协议最好直接约定 UTF-8,并在 Content-Type 里写明 charset=utf-8。

面试官追问

追问 1页面上中文显示正常,写进日志就乱码,说明是日志的问题吗?
参考回答

不一定,只能说明页面那条路径的编解码是对的。可能是日志写入时编码不对,也可能是你看日志的终端或编辑器用了 GBK,先 hexdump 看文件里的字节是不是 UTF-8 再下结论。

追问 2看到「锟斤拷」这种乱码,大概能判断出什么?
参考回答

说明数据被错误解码过两次。先按 UTF-8 解码失败变成了替换字符 U+FFFD,它的 UTF-8 字节是 ef bf bd,两个连起来再按 GBK 读就是「锟斤拷」。到这一步原始数据已经丢了,只能回源头查。

追问 3乱码字符串能不能转回来?
参考回答

看损坏程度。UTF-8 字节被当 GBK 解出来的「浣犲ソ」,再按 GBK 编码回去、按 UTF-8 解码通常能恢复;已经变成 � 的就不行了,信息在解码那一步就丢了。

追问 4用户名里的 emoji 存进 MySQL 后变成了问号,是编解码问题吗?
参考回答

多半是表或连接用的 utf8,它在 MySQL 里最多只存 3 个字节,emoji 要 4 个字节。改成 utf8mb4,表、字段和连接参数都要改。

# 写在前面

30 秒速记

  • 服务端每天都在做编解码:读请求体、读写文件、写数据库、返回响应
  • 网络和磁盘上只有字节,「字符串」是按某种编码解释出来的结果
  • 英文正常不代表没问题:ASCII 范围内 UTF-8、GBK 的字节一样,中文才会暴露差异
  • 二进制(图片、压缩包)不要转字符串,全程保持 Buffer
  • 解决乱码的思路永远是:找到原始字节,确认它真实的编码,用对应的规则解码

服务端开发绕不开字符编解码,因为网络和磁盘上流动的只有字节,字符串是按某种编码规则解释出来的。 读请求体、读文件、查数据库、写响应,每一步都在字节和字符串之间转换,只要有一步用错规则就是乱码。很多人遇到乱码就挨个试 toString('utf8')、toString('latin1'),其实效率很低。正确的思路是先确认手上这段字节到底是按什么编码生成的,再用同一套规则解回来。还有个常见误判:英文没问题不代表编码对了,因为英文字母在 UTF-8 和 GBK 里字节是一样的。

在web服务端开发中,字符的编解码几乎每天都要打交道。编解码一旦处理不当,就会出现令人头疼的乱码问题。

不少从事node服务端开发的同学,由于对字符编码码相关知识了解不足,遇到问题时,经常会一筹莫展,花大量的时间在排查、解决问题。

文本先对字符编解码的基础知识进行简单介绍,然后举例说明如何在node中进行编解码,最后是服务端的代码案例。

面试官追问

追问 1接口收英文一直正常,收中文就乱码,后端说网络没问题,你怎么看?
参考回答

这恰好说明是编码不一致。ASCII 范围的字符在 UTF-8、GBK 里字节完全相同,怎么解都对;中文的字节不同,规则一错就乱。

追问 2上传接口文本和图片走同一条链路,代码里先统一 toString() 再处理,有什么问题?
参考回答

图片会被损坏。图片字节里有大量不合法的 UTF-8 序列,toString() 会把它们替换成 U+FFFD,再转回 Buffer 已经不是原图了。二进制全程保持 Buffer,只有确定是文本才解码。

追问 3要导入一批历史 GBK 文件,产品要求统一按 UTF-8 读,能直接这么干吗?
参考回答

不行,GBK 字节按 UTF-8 解出来就是一堆 �,而且无法恢复。导入时用 iconv.decode(buf, 'gbk') 先转成字符串,系统内部再统一存 UTF-8。

追问 4线上用户名偶发乱码,前端、后端、数据库三方都说自己没问题,怎么定位?
参考回答

沿链路在每个边界打出字节:请求进来的原始 Buffer、写库前的字符串、库里存的字节、响应出去的字节。第一个对不上的位置就是问题所在,只看最终页面是分不出来的。

# 关于字符编解码

30 秒速记

  • 网络里传的永远是二进制,不管是中文、英文、文本还是图片
  • 编码:字符串 → 字节(客户端发送前);解码:字节 → 字符串(服务端收到后)
  • 字节本身不带「我是什么编码」的信息,双方必须事先约定,或者在 Content-Type 的 charset 里声明
  • Node 里对应 Buffer.from(str, 'utf8') 编码、buf.toString('utf8') 解码

编解码就是字符和二进制之间的互相翻译:发送方把字符串编码成字节传出去,接收方再把字节解码回字符串。 比如客户端发「你好」,网络上实际跑的是 e4 bd a0 e5 a5 bd 这 6 个字节,服务端要按 UTF-8 解码才能还原成「你好」。关键在于字节本身不会告诉你它是用什么编码的,所以双方必须提前约定好,或者在 HTTP 头里写明 charset。约定不一致,同样的字节就会被翻译成完全不同的字。

const bytes = Buffer.from('你好', 'utf8') // 编码
console.log(bytes) // <Buffer e4 bd a0 e5 a5 bd>
console.log(bytes.toString('utf8')) // 你好  解码
@前端进阶之旅: 代码已经复制到剪贴板

在网络通信的过程中,传输的都是二进制的比特位,不管发送的内容是文本还是图片,采用的语言是中文还是英文。

举个例子,客户端向服务端发送"你好"。

客户端 — 你好 —> 服务端

这中间包含了两个关键步骤,分别对应的是编码、解码。

  1. 客户端:将"你好"这个字符串,编码成计算机网络需要的二进制比特位。
  2. 服务端:将接收到的二进制比特位,解码成"你好"这个字符串。

总结一下:

  1. 编码:将需要传送的数据,转成对应的二进制比特位。
  2. 解码:将二进制比特位,转成原始的数据。

上面有些重要的技术细节没有提到,答案在下一小节。

  • 客户端怎么知道"你好"这个字符对应的比特位是多少?
  • 服务端收到二进制比特位之后,怎么知道对应的字符串是什么?

面试官追问

追问 1抓包看到的只是一串字节,服务端凭什么知道它是中文?
参考回答

它自己不知道,靠约定。HTTP 里一般是 Content-Type: text/plain; charset=utf-8 声明,没声明就用双方约定的默认值,新系统一律 UTF-8。

追问 2data 事件里每个 chunk 直接 toString(),大请求里中文偶尔变成 �,为什么?
参考回答

一个汉字在 UTF-8 里占 3 个字节,网络分块可能正好把它切成两半,单独解码哪一半都不是合法字符。要么先收齐再 Buffer.concat(chunks).toString(),要么 req.setEncoding('utf8'),它内部用 StringDecoder 处理跨块的残缺字节。

追问 3Buffer.byteLength('你好') 和 '你好'.length 分别是多少?
参考回答

前者是 6,UTF-8 编码后的字节数;后者是 2,JS 字符串的 UTF-16 码元数。限制上传大小、算 Content-Length 要用字节数,用 length 会算少。

追问 4网关对图片也做一次「解码再转发」,会出什么问题?
参考回答

图片不是文本,按 UTF-8 解码时不合法的字节会被替换成 U+FFFD,再编码回去就不是原图了。二进制要原样透传,只看 Content-Type 决定怎么处理。

# 关于字符集和字符编码

← 日志模块morgan测试用例mocha,should,istanbul →

fe